﻿using System;
using System.Collections.Generic;
using System.Linq;
using YawetagLib.Data.Mapping;
using YawetagLib.Utilities;

namespace YawetagLib.Data
{
    public sealed class DataManager : IDataManager
    {
        private readonly object _lock = new object();

        public DataManager(IDataContextFactory dataContextFactory)
        {
            DataContextFactory = dataContextFactory;

            Repositories = new Dictionary<Type, IRepository>();
        }

        public TRepository Get<TRepository>(Guid dataContextIdentifier) where TRepository : IRepository
        {
            Type repositoryType = TypeResolver.Get(typeof(TRepository));

            lock (_lock)
            {
                if (!Repositories.ContainsKey(typeof(TRepository)))
                {
                    Repositories.Add(typeof(TRepository), GetRepositoryInstance(repositoryType, dataContextIdentifier));
                }
            }

            return (TRepository)Repositories[typeof(TRepository)];
        }

        public TRepository Get<TRepository>() where TRepository : IRepository
        {
            return Get<TRepository>(Guid.Empty);
        }

        private IRepository GetRepositoryInstance(Type repositoryType, Guid dataContextIdentifier)
        {
            return (IRepository)Activator.CreateInstance(repositoryType, GetDataContextForRepository(repositoryType, dataContextIdentifier));
        }

        private IDataContext GetDataContextForRepository(Type repositoryType, Guid dataContextIdentifier)
        {
            DataContextMappingAttribute mapping = GetDataContextMapping(repositoryType);

            return mapping != null 
                ? GetDataContext(mapping.DataContextType, dataContextIdentifier) 
                : GetDefaultDataContext();
        }

        private static DataContextMappingAttribute GetDataContextMapping(Type repositoryType)
        {
            Type entityType = ExtractCrudRepositoryEntityType(repositoryType);

            return entityType != null
                ? entityType.GetSingleAttribute<DataContextMappingAttribute>()
                : repositoryType.GetSingleAttribute<DataContextMappingAttribute>();
        }

        private static Type ExtractCrudRepositoryEntityType(Type repositoryType)
        {
            return repositoryType
                .GetInterfaces()
                .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICrudRepository<>))
                .Select(i => i.GetGenericArguments()[0])
                .FirstOrDefault();
        }

        private IDataContext GetDefaultDataContext()
        {
            return DataContextFactory.GetDefault();
        }

        private IDataContext GetDataContext(Type dataContextType, Guid dataContextIdentifier)
        {
            return DataContextFactory.Get(DataContextToken.New(dataContextType, dataContextIdentifier));
        }

        private IDataContextFactory DataContextFactory { get; set; }

        private IDictionary<Type, IRepository> Repositories { get; set; }
    }
}
